1005663861 发表于 2022-10-21 10:56:25

请问一下类中的专有属性、专有变量

from abc import ABC, abstractclassmethod
from collections import namedtuple

Customer = namedtuple('Customer', 'name fidelity')

class LineItem:
    def __init__(self, product, quantity, price):
      self.product = product
      self.quantity = quantity
      self.price = price

    def total(self):
      return self.price * self.quantity


class Order:
    def __init__(self, customer, cart, promotion=None):
      self.customer = customer
      self.cart = list(cart)
      self.promotion = promotion

    def total(self):
      if not hasattr(self, '__total'):
            self.__total = sum(item.total() for item in self.cart)
      return self.__total

    def due(self):
      if self.promotion is None:
            discount = 0
      else:
            discount = self.promotion.discount(self)
      return self.total() - discount

    def __repr__(self):
      fmt = '<Order total: {:.2f} due: {:.2f}>'
      return fmt.format(self.total(), self.due())


class Promotion(ABC):

    @abstractclassmethod
    def discount(self, order):
      """返回折扣金额(正值)"""


class FidelityPromo(Promotion):
    """为积分为1000或以上的顾客提供%5的折扣"""

    def discount(self, order):
      return order.total() * .05 if order.customer.fidelity >= 1000 else 0


class BulkItemPromo(Promotion):
    """单个商品为20个以上时提供10%折扣"""

    def discount(self, order):
      discount = 0
      for item in order.cart:
            if item.quantity >= 20:
                discount += item.total() * .1
      return discount


class LargeOrderPromo(Promotion):
    """订单中的不同商品达到10个以上时提供7%折扣"""
    def discount(self, order):
      distinct_items = {item.product for item in order.cart}
      if len(distinct_items) >= 10:
            return order.total() * .07
      return 0

代码如上,请教一下第24行中的,__total是类中的专属变量,如果将此变量更改为不带下划线的普通变量,程序就会报错,请问这是为什么?

TypeError: 'float' object is not callable

jackz007 发表于 2022-10-21 11:17:23

本帖最后由 jackz007 于 2022-10-21 11:21 编辑

         因为你的类中有名为 total 的方法,
class LineItem:
. . . . . .
    def total(self):
. . . . . .
class Order:
. . . . . .
    def total(self):
      如果去掉下划线,就会和函数名冲突,在为 total 赋值以后,函数的调用地址就被覆盖了,调用 total() 的时候,Python 就会告诉你,浮点对象不可以被 calll。

1005663861 发表于 2022-10-21 14:54:02

本帖最后由 1005663861 于 2022-10-21 14:55 编辑

jackz007 发表于 2022-10-21 11:17
因为你的类中有名为 total 的方法,

      如果去掉下划线,就会和函数名冲突,在为 total...

那如果后边的函数名,从total改为其他的话,为什么也不行

jackz007 发表于 2022-10-21 15:00:50

本帖最后由 jackz007 于 2022-10-21 15:04 编辑

1005663861 发表于 2022-10-21 14:54
那如果后边的函数名,从total改为其他的话,为什么也不行

      如果改方法名,需要同时改定义和调用,显然,如果只改定义,那么调用就会落空,最好通过文本搜索 "total" 进行定位,一定要改彻底,一个不漏。

1005663861 发表于 2022-10-21 15:05:05

jackz007 发表于 2022-10-21 15:00
如果改方法名,需要同时改定义和调用,显然,如果只改定义,那么调用就会落空,最好通过文本搜 ...

是的,一个不漏

jackz007 发表于 2022-10-21 15:07:36

1005663861 发表于 2022-10-21 15:05
是的,一个不漏

         不可能,上错误信息。

1005663861 发表于 2022-10-21 15:18:49

jackz007 发表于 2022-10-21 15:07
不可能,上错误信息。

rom abc import ABC, abstractclassmethod
from collections import namedtuple

Customer = namedtuple('Customer', 'name fidelity')

class LineItem:
    def __init__(self, product, quantity, price):
      self.product = product
      self.quantity = quantity
      self.price = price

    def total(self):
      return self.price * self.quantity


class Order:
    def __init__(self, customer, cart, promotion=None):
      self.customer = customer
      self.cart = list(cart)
      self.promotion = promotion

    def total1(self):
      if not hasattr(self, '__total'):
            self.total1 = sum(item.total() for item in self.cart)
      return self.total1

    def due(self):
      if self.promotion is None:
            discount = 0
      else:
            discount = self.promotion.discount(self)
      return self.total1() - discount

    def __repr__(self):
      fmt = '<Order total: {:.2f} due: {:.2f}>'
      return fmt.format(self.total1(), self.due())


class Promotion(ABC):

    @abstractclassmethod
    def discount(self, order):
      """返回折扣金额(正值)"""


class FidelityPromo(Promotion):
    """为积分为1000或以上的顾客提供%5的折扣"""

    def discount(self, order):
      return order.total1() * .05 if order.customer.fidelity >= 1000 else 0


class BulkItemPromo(Promotion):
    """单个商品为20个以上时提供10%折扣"""

    def discount(self, order):
      discount = 0
      for item in order.cart:
            if item.quantity >= 20:
                discount += item.total() * .1
      return discount


class LargeOrderPromo(Promotion):
    """订单中的不同商品达到10个以上时提供7%折扣"""
    def discount(self, order):
      distinct_items = {item.product for item in order.cart}
      if len(distinct_items) >= 10:
            return order.total1() * .07
      return 0

jackz007 发表于 2022-10-21 15:24:56

本帖最后由 jackz007 于 2022-10-21 15:26 编辑

1005663861 发表于 2022-10-21 15:18


         我不会用你的代码,你不是说改方法名不行吗,我要的是你运行中得到的错误信息。

1005663861 发表于 2022-10-21 15:47:33

jackz007 发表于 2022-10-21 15:24
我不会用你的代码,你不是说改方法名不行吗,我要的是你运行中得到的错误信息。

TypeError: 'float' object is not callable

jackz007 发表于 2022-10-21 16:02:04

本帖最后由 jackz007 于 2022-10-21 16:10 编辑

1005663861 发表于 2022-10-21 15:47
TypeError: 'float' object is not callable

      你自己认为问题会出在哪里???

      真的全部都改了
class LineItem:
. . . . . .
    def total(self):

class Order:
. . . . . .
    def total1(self):
      if not hasattr(self, '__total'):
            self.total1 = sum(item.total() for item in self.cart)

class BulkItemPromo(Promotion):
. . . . . .
    def discount(self, order):
. . . . . .
                discount += item.total() * .1

1005663861 发表于 2022-10-21 16:23:31

jackz007 发表于 2022-10-21 16:02
你自己认为问题会出在哪里???

      真的全部都改了

我不知道啊,我把hasattr()里面的第二个参数也改了,还是报错,

TypeError: unsupported operand type(s) for -: 'method' and 'int'

jackz007 发表于 2022-10-21 16:31:08

1005663861 发表于 2022-10-21 16:23
我不知道啊,我把hasattr()里面的第二个参数也改了,还是报错,

TypeError: unsupported operand ty ...

         我就不得不好奇,你没有哪个能力干嘛要找这个麻烦?就因为看 __total 不顺眼?

阿奇_o 发表于 2022-10-21 16:40:45

本帖最后由 阿奇_o 于 2022-10-21 17:18 编辑

去掉折扣部分,我直接补全,让代码可以运行,让你看看效果和原因:
from collections import namedtuple

Customer = namedtuple('Customer', 'name fidelity')


class Order:
    def __init__(self, customer, cart, promotion=None):
      self.customer = customer
      self.cart = cart
      self.promotion = promotion

    def total(self):
      if not hasattr(self, '__total'):
            self.__total = sum(item.price * item.quantity for item in self.cart)
      return self.__total
    # def total(self):
    #   if not hasattr(self, 'total'):# 始终存在 total,因为你定义的名字就叫 total
    #         self.total = sum(item.price * item.quantity for item in self.cart)
    #   return self.total# 这是直接返回该方法本身了!


class Cart:
    def __init__(self, items=None):
      if items:
            self.items = items
   
    def __len__(self):
      return len(self.items)

    def __getitem__(self, position):
      return self.items


if __name__ == "__main__":
    c1 = Customer('张三', 1000)

    Item = namedtuple('Item', 'product price quantity')
    it1, it2 = Item('A', 100, 2), Item('B', 200, 1)
    cart = Cart(items=)

    print(cart.items)
    print(cart, cart.price, cart.quantity)
   
    o = Order(customer=c1, cart=cart)
    print(f"{o.customer.name} 的这个订单,合计:¥ {o.total()} 元")
ps:
其实,我觉得 Order 里 total 不用定义为一个实例方法,也不用搞什么私有变量,直接定义为一个实例变量self.total即可,如
      if self.cart:
            self.total = sum(item.price * item.quantity for item in self.cart)
      else:
            self.total = 0
毕竟,订单了创建 一般就意味着 购物车不是空的了(里有商品条目了)。



1005663861 发表于 2022-10-21 17:13:16

jackz007 发表于 2022-10-21 16:31
我就不得不好奇,你没有哪个能力干嘛要找这个麻烦?就因为看 __total 不顺眼?

想知道其中的原理是什么,就需要尝试一下,更改

jackz007 发表于 2022-10-21 17:25:10

1005663861 发表于 2022-10-21 17:13
想知道其中的原理是什么,就需要尝试一下,更改

      这个是我修改的,用这个代码再测试呢。
from abc import ABC, abstractclassmethod
from collections import namedtuple

Customer = namedtuple('Customer', 'name fidelity')

class LineItem:
    def __init__(self, product, quantity, price):
      self.product = product
      self.quantity = quantity
      self.price = price

    def totalx(self):
      return self.price * self.quantity


class Order:
    def __init__(self, customer, cart, promotion=None):
      self.customer = customer
      self.cart = list(cart)
      self.promotion = promotion

    def totalx(self):
      if not hasattr(self, 'total'):
            self.total = sum(item.totalx() for item in self.cart)
      return self.total

    def due(self):
      if self.promotion is None:
            discount = 0
      else:
            discount = self.promotion.discount(self)
      return self.totalx() - discount

    def __repr__(self):
      fmt = '<Order total: {:.2f} due: {:.2f}>'
      return fmt.format(self.totalx(), self.due())


class Promotion(ABC):

    @abstractclassmethod
    def discount(self, order):
      """返回折扣金额(正值)"""


class FidelityPromo(Promotion):
    """为积分为1000或以上的顾客提供%5的折扣"""

    def discount(self, order):
      return order.totalx() * .05 if order.customer.fidelity >= 1000 else 0


class BulkItemPromo(Promotion):
    """单个商品为20个以上时提供10%折扣"""

    def discount(self, order):
      discount = 0
      for item in order.cart:
            if item.quantity >= 20:
                discount += item.totalx() * .1
      return discount


class LargeOrderPromo(Promotion):
    """订单中的不同商品达到10个以上时提供7%折扣"""
    def discount(self, order):
      distinct_items = {item.product for item in order.cart}
      if len(distinct_items) >= 10:
            return order.totalx() * .07
      return 0

Brick_Porter 发表于 2022-10-21 17:35:40

你的问题中Order这个类的total可以写成函数形式,也可以写成self.total的形式,就像前面的人说的那样。

而且看了你的代码我觉得有些问题,你虽然使用了ABC进行抽象,但是似乎自己都没有规划好每个接口应该具备怎样的能力。从设计模式的角度来说,你虽然使用了接口,但是却违背了里氏替换原则和依赖倒置原则。所以我把你的代码顺序进行了大幅改动,逻辑微调(就是Order类的total方法那个部分),保留了你要的total,也删除了__total这个私有属性,希望修改后的代码能对你有用。另外请注意,我使用了Python 3.10中的新特性来标注参数类型与返回值类型:from abc import ABCMeta, abstractmethod
from collections import namedtuple

# ----------抽象层,定义各种接口----------
Customer = namedtuple('Customer', 'name fidelity')


class InterfaceItem(metaclass=ABCMeta):
    """商品接口,子类须具备product、price、quantity属性,
    必须实现total方法
    """

    product: str
    price: int | float
    quantity: int | float

    @abstractmethod
    def total(self) -> int | float:
      """商品总价"""
      pass


class InterfaceOrder(metaclass=ABCMeta):
    """订单接口,子类须具备cart、customer、total属性,
    且必须实现due方法
    """

    cart: list
    customer: Customer

    @property
    @abstractmethod
    def total(self) -> int | float:
      """订单商品的总金额"""
      pass

    @abstractmethod
    def due(self) -> int | float:
      """应该支付金额"""
      pass


class InterfacePromotion(metaclass=ABCMeta):
    """促销活动接口,子类必须实现discount方法"""

    @abstractmethod
    def discount(self, order: InterfaceOrder) -> int | float:
      """返回折扣金额(正值)"""
      pass


# ----------实现层,实现各种接口----------
class LineItem(InterfaceItem):
    """具体商品,实现IterfaceItem接口"""

    def __init__(
      self,
      product: str,
      quantity: int | float,
      price: int | float
    ) -> None:
      self.product = product
      self.quantity = quantity
      self.price = price

    def total(self) -> int | float:
      return self.price * self.quantity


class Order(InterfaceOrder):
    """具体订单,实现IterfaceOrder接口"""

    def __init__(
            self,
            customer: Customer,
            *cart: InterfaceItem,
            promotion: InterfacePromotion = None
    ) -> None:
      self.customer = customer
      self.cart = list(cart)
      self.promotion = promotion

    @property
    def total(self) -> int | float:
      if self.cart:
            return sum(item.total() for item in self.cart)
      return 0

    def due(self) -> int | float:
      if self.promotion is None:
            discount = 0
      else:
            discount = self.promotion.discount(self)
      return self.total - discount

    def __repr__(self):
      fmt = '<Order total: {:.2f} due: {:.2f}>'
      return fmt.format(self.total, self.due())


class FidelityPromo(InterfacePromotion):
    """为积分为1000或以上的顾客提供%5的折扣"""

    def discount(self, order: InterfaceOrder) -> int | float:
      return order.total * .05 if order.customer.fidelity >= 1000 else 0


class BulkItemPromo(InterfacePromotion):
    """单个商品为20个以上时提供10%折扣"""

    def discount(self, order: InterfaceOrder) -> int | float:
      discount = 0
      for item in order.cart:
            if item.quantity >= 20:
                discount += item.total() * .1
      return discount


class LargeOrderPromo(InterfacePromotion):
    """订单中的不同商品达到10个以上时提供7%折扣"""

    def discount(self, order: InterfaceOrder) -> int | float:
      distinct_items = {item.product for item in order.cart}
      if len(distinct_items) >= 10:
            return order.total * .07
      return 0


# ----------业务层,处理具体业务逻辑----------
def business() -> None:
    # 实例化各种促销方案
    fp: InterfacePromotion = FidelityPromo()

    c1: Customer = Customer('张三', 1000)
    goods: list = [
      LineItem('商品A', 20, 126), LineItem('商品B', 11, 51.8), LineItem('商品C', 3, 26)
    ]
    order: Order = Order(c1, *goods, promotion=fp)
    print(order)


if __name__ == '__main__':
    business()
页: [1]
查看完整版本: 请问一下类中的专有属性、专有变量