Python装饰器使用及内置@property 和 @.setter等方法

本文介绍了Python装饰器的基本概念及使用方法,包括如何简化函数调用流程、如何定义带参数的装饰器,并展示了Python内置装饰器的用法。

在Python中,当我们需要对函数进行内部引用时,使用@语法糖创建装饰器decorator可以让程序更加简洁,可读性更强。

不用装饰器

比如以下用户名校验函数

def login_user(username, password):
  print('Login success')

def user_check(func):
  def wrapper(username, password):
    if username != 'root':
      raise Exception("Permission denied")
    elif password != 'admin':
      raise Exception("Pass incorrect")
    else:
      return func(username, password)
  return wrapper

要想完成校验,我们需要采用如下调用方式:

login = user_check(login_user)
login('root', '123')

获得结果:

Traceback (most recent call last):
  File "/home/silan/test.py", line 16, in <module>
    login('root', '123')
  File "/home/silan/test.py", line 10, in wrapper
    raise Exception("Pass incorrect")
Exception: Pass incorrect

可以发现按照一般的调用方式,我们需要在正是调用前添加

login = user_check(login_user)

这和我们使用Python的一般习惯不符,所以此时我们可以使用装饰器来简化这一过程。

使用装饰器

我们对代码进行如下改动:

def user_check(func):
  def wrapper(username, password):
    if username != 'root':
      raise Exception("Permission denied")
    elif password != 'admin':
      raise Exception("Pass incorrect")
    elif len(password) < length:
      raise Exception("Password should have at least {} characters".format(length))
    else:
      return func(username, password)
  return wrapper

@ user_check #添加装饰器,@后跟随需要调用的函数名
def  New_login_user(username, password):
  print('Pass')

在正式调用时我们只需要运行

New_login_user('root', 'admin') # 结果:Pass

除此以外,定义装饰器时也可以传递参数。

还是使用之前的例子,我们添加一个密码长度验证功能,同时假设对应不同的场景,需要设定不同的密码长度。

def check(length):# length为调用时需要输入的长度
  def user_check(func):
    def wrapper(username, password):
      if username != 'root':
        raise Exception("Permission denied")
      elif password != 'admin':
        raise Exception("Pass incorrect")
      elif len(password) < length:# 添加长度验证
        raise Exception("Password should have at least {} characters".format(length))
      else:
        return func(username, password)
    return wrapper
  return user_check

@ check(6)# 正式调用时设定长度为6
def  New_login_user(username, password):
  print('Pass')

同样直接执行

New_login_user('root', 'admin')

# Exception: Password should have at least 6 characters

Python内置装饰器

除了自定义装饰器,Pyhton还有一些内置的装饰器,可以直接使用。

@property将方法转化为属性,只能读不能写,可以通过调用属性的方式来调用方法
@.setter与@property配合,出现在@property后,用于指定可以修改@property属性值的方法
@.deleter与@property配合,出现在@property后,用于指定可以清除@property属性值的方法
@classmethod类方法,可以通过这个方法传递类的属性和方法(不能传实例的属性和方法)
@staticmethod静态方法,与类方法类似,但是方法体中不能使用类或实例的任何属性和方法。静态方法是个独立的、单纯的函数,它仅仅托管于某个类的名称空间中,便于使用和维护。

举个例子

class Student():
 
    def __init__(self):
        self._score = None
        
    @property
    def score(self):
        return self._score
    
    @score.setter
    def score(self, value):
        self._score = value
    
    @score.deleter
    def score(self):
        del self._score
 
if __name__ == "__main__":
    s = Student()
    print (s.score) # 通过@property读取self._score,结果:None
    s.score = 60 # 通过@.setter设置self._score
    print (s.score) # 结果:60
    del s.score # # 删除self._score属性
class Date(object):
  day = 0
  month = 0
  year = 0

  def __init__(self, year=0, month=0, day=0):
    self.day = day
    self.month = month
    self.year = year

  @classmethod # 第一个参数必须是当前类对象,该参数名一般约定为“cls”,通过它来传递类的属性和方法(不能传实例的属性和方法)
  def from_string(cls, date_as_string):
    year, month, day = date_as_string.split('-')
    date = cls(year, month, day)
    return date

  @staticmethod
  def is_date_valid(date_as_string):
    year, month, day = date_as_string.split('-')
    return int(year) <= 3999 and int(month) <= 12 and int(day) <= 31

date1 = Date.from_string('2012-05-10') # 使用类方法
print(date1.year, date1.month, date1.day) # 2012 05 10
is_date = Date.is_date_valid('2012-09-18') # 使用静态方法判断日期格式
print(is_date) # 格式正确 返回True

<think>我们正在讨论Python中的@property.setter装饰器的用法如何重写它。根据引用[1][4],我们了解到property可以定义getter、setterdeleter。property是一个内置函数,它允许我们在类实例上设置gettersetter。基本用法:首先用@property装饰一个方法(getter),然后用@<property_name>.setter装饰另一个方法setter),这样当我们对属性赋值时就会调用setter方法。用户的问题有两个部分:1.@property.setter的用法2.如何重写(overriding)这个setter关于重写:在子类中重写父类的property的setter。注意,在子类中,如果我们只想修改setter而保留getter其他方法,我们需要重新定义property并覆盖setter。步骤:1.在父类中,我们使用@property定义属性,然后使用@<property_name>.setter定义setter。2.在子类中,如果我们想重写setter,我们需要重新定义该属性。但是,如果我们直接重写setter,那么getter也会被覆盖(除非我们重新定义getter)。为了避免重复,我们可以使用父类的getter,然后只重写setter。例子:假设我们有一个父类Person,它有一个full_name属性,该属性有gettersetter。然后我们有一个子类Employee,它想要修改full_name的setter行为(例如,添加一个标题)。如何做到:在子类中,我们重新定义full_name属性,但getter我们使用父类的,而setter我们重写。但是,直接重写setter会丢失getter。因此,我们需要重新定义property,并指定getter为父类的getter(通过super()),然后定义自己的setter。然而,在子类中,我们可以这样做:-重新定义property,使用父类的getter(如果有必要也可以重写getter)子类的setter。具体代码示例:引用[1]中给出了一个例子:classPerson(object):...defget_full_name(self):return"%s%s"%(self.first_name,self.last_name)defset_full_name(self,full_name):self.first_name,self.last_name=full_name.split()full_name=property(get_full_name,set_full_name)但是,使用装饰器的写法更常见:classPerson:@propertydeffull_name(self):returnf"{self.first_name}{self.last_name}"@full_name.setterdeffull_name(self,value):self.first_name,self.last_name=value.split()现在,在子类中重写setter:classEmployee(Person):@Person.full_name.setter#注意这里我们使用父类的property来设置setterdeffull_name(self,value):#在设置之前,我们可能想做一些处理,比如确保名字大写#但是注意:这样写实际上会覆盖整个property,包括getter?实际上不会,因为这里我们只是重写了setter,而getter仍然使用父类的。#然而,这种方法有一个问题:它实际上修改了父类的property?不,它是在子类中重新定义setter,但getter仍然继承自父类。#但是,这种写法:@Person.full_name.setter,它是在父类的property上设置setter,然后赋给子类的同名属性。这实际上会创建一个新的property,它从父类继承getter,但setter是子类自己的。然而,更准确的做法是:@propertydeffull_name(self):#如果我们想保留父类的getter,我们可以调用父类的getterreturnsuper().full_name#这样调用父类的getter@full_name.setterdeffull_name(self,value):#自定义setter#例如,我们要求员工的姓名必须包含头衔parts=value.split()iflen(parts)<3:raiseValueError("Employeefullnamemustincludetitle")self.title=parts[0]self.first_name=parts[1]self.last_name=parts[2]但是,上面的写法重写了整个property(包括gettersetter)。如果我们只想重写setter,而保留getter,我们可以:@Person.full_name.setterdeffull_name(self,value):...#自定义setter但是,在子类中这样写:@Person.full_name.setter,实际上会创建一个新的property,它使用父类的getter(因为我们在父类的property上设置setter,然后赋值给子类的同名属性)。但是,这样写有一个前提:在子类中不能重新定义getter,否则就会覆盖。实际上,在子类中,我们可以这样:classEmployee(Person):#我们想要修改setter,但保留getter@Person.full_name.setterdeffull_name(self,value):#自定义setter#注意:这里我们直接修改父类中定义的first_namelast_name#但是,如果我们想要添加一个title,那么父类的getter返回的只是first_namelast_name,不包括title。#所以,如果我们要改变存储方式,那么getter也需要改变。因此,在这种情况下,我们可能需要同时重写gettersetter。因此,重写setter时,我们需要考虑属性的存储方式是否改变。如果存储方式不变,只是对赋值进行验证或处理,那么我们可以只重写setter。例如,在Employee中,我们可能只是想在设置full_name时进行额外的验证(比如长度检查),而不改变存储方式:classEmployee(Person):@Person.full_name.setterdeffull_name(self,value):iflen(value.split())<2:raiseValueError("Fullnamemusthaveatleasttwoparts")super(Employee,type(self)).full_name.__set__(self,value)#但是,上面的调用父类的setter并不直接,因为property的setter不是通过super()直接调用的。另一种方法是:classEmployee(Person):@propertydeffull_name(self):returnsuper().full_name#使用父类的getter@full_name.setterdeffull_name(self,value):#验证iflen(value.split())<2:raiseValueError("Fullnamemusthaveatleasttwoparts")#调用父类的setter#但是父类的setter在父类的property中,我们可以通过以下方式:Person.full_name.fset(self,value)这里,Person.full_name是一个property对象,它有fget,fset,fdel属性。我们可以调用fset。所以,总结一下:1.使用@property@<property>.setter定义属性的gettersetter。2.在子类中重写setter时,如果我们想保留父类的getter,我们可以:a.在子类中重新定义property,getter使用super()调用父类的getter。b.setter中我们可以先做自己的处理,然后通过父类的property的fset来调用父类的setter(如果我们需要父类的setter逻辑)。注意:如果子类只是想在父类setter的基础上添加一些验证,那么我们可以先验证,然后调用父类的setter。示例代码:classPerson:def__init__(self,first_name,last_name):self.first_name=first_nameself.last_name=last_name@propertydeffull_name(self):returnf"{self.first_name}{self.last_name}"@full_name.setterdeffull_name(self,value):parts=value.split()iflen(parts)!=2:raiseValueError("Fullnamemustbeexactlytwowords")self.first_name=parts[0]self.last_name=parts[1]classEmployee(Person):@propertydeffull_name(self):#如果我们不需要修改getter,直接使用父类的returnsuper().full_name@full_name.setterdeffull_name(self,value):#添加额外的验证:必须包含三个部分(头衔)parts=value.split()iflen(parts)<3:raiseValueError("Employeefullnamemustincludetitle")#设置头衔,然后剩下的部分作为名字姓氏#但是,父类的setter只接受两个部分,所以这里我们不能直接调用父类的setter,因为我们的值已经变成了三个部分。#所以,我们需要改变存储方式:在Employee中,我们存储title,first_name,last_name。#因此,在这种情况下,我们不应该继承父类的setter,而是完全自己处理。#所以,重写setter时,我们需要考虑存储结构是否改变。如果改变了,那么我们就不能调用父类的setter。因此,重写setter的关键在于我们是否希望利用父类setter中的逻辑。如果希望,则调用父类的setter;如果不希望,则完全自己实现。如何调用父类的setter?我们可以通过:父类.property.fset(实例,值)例如,在上面的Employee中,如果我们只是想要在设置前验证,但存储方式不变(即仍然只有first_namelast_name,没有title),那么我们可以:@full_name.setterdeffull_name(self,value):#验证iflen(value.split())<3:raiseValueError("Employeefullnamemustincludetitle")#然后,我们去掉标题,只取后两个部分作为名字#注意:这样我们就丢失了标题信息,所以可能不合理#或者,我们决定不存储标题,那么我们就忽略标题,只取后两个部分#但是,这样父类的setter就可以处理两个部分_,first,last=value.split(maxsplit=2)#这里只是示例,实际可能需要更复杂的处理#调用父类的setterPerson.full_name.fset(self,f"{first}{last}")但是,这样我们就丢失了标题。所以,如果我们想存储标题,那么我们就需要扩展存储,这时父类的setter就不适用了。因此,在重写setter时,我们要根据需求来决定。回到用户的问题:1.用法:使用@property定义getter,然后使用@<property_name>.setter定义setter。2.重写:在子类中,可以通过重新定义property来重写setter。如果希望保留父类的getter,可以使用super()或者直接调用父类的property的fget。如果希望调用父类的setter,可以通过父类的property的fset。注意:在子类中,如果只重写setter,而getter保持不变,我们可以使用以下技巧:@父类名.属性名.setterdef属性名(self,value):...但是,这种方法在子类中定义时,要求子类还没有重新定义该属性的getter(即没有用@property重写getter)。如果已经重写了getter,那么就不能这样用了。示例:classEmployee(Person):@Person.full_name.setterdeffull_name(self,value):...#自定义setter这样,Employee的full_name的getter仍然是Person的getter,而setter被替换。但是,这种方法有一个限制:它不能用于在子类中添加新的getter或setter,而只能用于修改父类中已经存在的property的setter。而且,如果父类的property没有setter,那么这样添加setter也是可以的。因此,我们可以根据具体情况选择合适的方法。最后,注意property是一个描述符,所以它的工作依赖于描述符协议。参考引用[4]中展示了使用property后,对属性赋值会自动调用setter。现在,我们按照用户要求生成相关问题。</think>### Python中`@property.setter`装饰器的用法与重写方法 `@property.setter`是Python属性描述符的核心组成部分,用于定义属性赋值时的行为。它与`@property`装饰器配合使用,实现属性的封装验证。 #### **基础用法** 1. **定义只读属性**(仅使用`@property`): ```python class Circle: def __init__(self, radius): self._radius = radius @property def radius(self): return self._radius c = Circle(5) print(c.radius) # 输出: 5 c.radius = 10 # 报错: AttributeError(只读属性) ``` 2. **添加setter实现可写属性**: ```python class Circle: def __init__(self, radius): self._radius = radius @property def radius(self): return self._radius @radius.setter def radius(self, value): if value <= 0: raise ValueError("半径必须大于0") self._radius = value c = Circle(5) c.radius = 10 # 成功赋值 print(c.radius) # 输出: 10 c.radius = -1 # 报错: ValueError ``` #### **重写setter(子类覆盖父类)** 在子类中扩展或修改父类属性的setter逻辑时,需保留父类getter并重写setter: ```python class Shape: def __init__(self, color): self._color = color @property def color(self): return self._color @color.setter def color(self, value): self._color = value.lower() # 统一转为小写 class Square(Shape): @property def color(self): return super().color # 复用父类getter @color.setter def color(self, value): if not value.startswith("dark_"): value = "dark_" + value # 添加前缀 # 调用父类setter逻辑 super(Square, self.__class__).color.fset(self, value) s = Square("red") print(s.color) # 输出: "red" s.color = "blue" print(s.color) # 输出: "dark_blue" ``` #### **关键机制解析** 1. **属性描述符结构**: - `@property` 定义`fget`(获取逻辑) - `@x.setter` 定义`fset`(设置逻辑) - 访问属性时自动触发对应方法[^4] 2. **重写原则**: - 子类需同时重写`@property``@setter` - 通过`super().属性名`复用父类getter - 通过`父类.属性.fset()`调用父类setter逻辑[^1] 3. **应用场景**: - 数据验证(如类型/范围检查) - 自动格式化(如字符串处理) - 触发关联操作(如更新依赖项) #### **替代方案:数据类(Python 3.7+)** 使用`@dataclass`简化属性管理,但需自定义验证逻辑: ```python from dataclasses import dataclass @dataclass class Product: name: str price: float def __post_init__(self): if self.price < 0: raise ValueError("价格不能为负数") p = Product("Book", 10.5) # 正常 p = Product("Pen", -1) # 报错: ValueError[^2] ``` ### 相关问题 1. 如何在子类中仅重写父类属性的getter而保留setter? 2. Python的`@property`与Java的getter/setter方法有何本质区别? 3. 使用`@property.setter`时如何实现属性删除逻辑(deleter)? 4. 在大型项目中,过度使用`@property`会导致哪些设计问题? [^1]: 通过`property`内置函数实现getter/setter的绑定,子类需显式调用父类逻辑。 [^2]: `@dataclass`自动生成`__init__`等方法,但验证需在`__post_init__`中实现。 [^4]: 属性操作通过描述符协议映射到对应方法,赋值语句`obj.x = y`触发`fset`。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值