在python中验证长链条件的python方法

The Pythonic way of validating a long chain of conditions in Python

所以我有一长串的条件应该被证实是真的。我没有把一个很长的if条件链接起来,而是试图"创新",这样做,我认为这样更易读。但我的问题是,这是最佳的方法吗?

或者有没有一种Python式的方法?附言:请用另一种回答而不是回答"不",谢谢!

这是代码块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def site_exists(site):
   """
    returns the sitebean if it exists,
    else returns false
   """

    vpadmin_service = _get_vpadmin_service(site)
    all_sites = VpAdminServiceUtil.getSites(vpadmin_service)
    for site_listing in all_sites:
        if site.getId():
            #condition check
            try:
                assert site.getId() == site_listing.getId()
                assert site.getName() == site_listing.getName()
                assert site.getCustomer().getId() == site_listing.getCustomer().getId()
            except AssertionError:
                continue
            #pass conditions
            return site_listing
        #no id, so just check for name and customer
        else:
            #condition check
            try:
                assert site.getName() == site_listing.getName()
                assert site.getCustomer().getId() == site_listing.getCustomer().getId()
            except AssertionError:
                continue
            #pass conditions
            site.setId(site_listing.getId())
            return site_listing
    return False


一种更简单的方法是构建条件的元组并比较这些元组:

1
2
3
4
5
6
7
def site_info(s):
    return s.getId(), s.getName(), s.getCustomer().getId()

if site_info(site) == site_info(site_listing):
    return site_listing
else:
    continue

如果您有很多条件,或者条件很昂贵,您可以为这些条件创建一个生成器,并与anyall进行比较:

1
2
3
4
5
6
7
8
9
10
import itertools
def iter_site_info(s):
    yield s.getId()
    yield s.getName()
    yield s.getCustomer().getId()

if all(x==y for (x, y) in itertools.izip(iter_site_info(site), iter_site_info(site_listing)):
    return site_listing
else:
    continue

我不确定Jython是否有anyall,但它们只是一些简单的函数。

edit-anyall出现在python 2.5中,所以jython应该有它们。


对控制流使用异常是错误的!它也会扼杀你的表演。假设条件在10个案例中的一个是真的?所以在最坏的情况下需要处理9个例外——这会带来巨大的性能成本。

如果需要可读性,请尝试:

1
2
3
4
5
6
        if (
            condition1 and \
            condition2 and \
            condition3
            ):
            # do something

对于修订版,这应等同于:

1
2
3
4
5
6
for site_listing in all_sites:
    if site.getName() == site_listing.getName() and site.getCustomer().getId() == site_listing.getCustomer().getId():
        if not site.getId():
            site.setId(site_listing.getId())
        if site.getId() == site_listing.getId():
            return site_listing


我将在site上实现一个is_same方法,并在for循环中调用它。

1
2
3
4
all_sites = VpAdminServiceUtil.getSites(vpadmin_service)
for site_listing in all_sites:
    if site_listing.is_same(site, set_id=True):
        return site_listing

关于它的实现(这是您真正的问题),我建议如下:

1
2
3
4
5
...
if self.getName()     != site.getName():     return False
if self.getCustomer() != site.getCustomer(): return False
...
return True

使用__eq__方法!如果你自己写的是site类,没问题。如果它来自您控制之外的模块,请参阅此解决方案,或者创建一个稍微不那么优雅的包装类。对于wrap变量,这将是类:

1
2
3
4
5
6
7
8
9
10
11
12
13
class WrappedSite(object):
    def __init__(self, site):
        self.site = site

    def __eq__(self, other):
        if site.getId():
            if self.site.getId() != other.site.getId():
                return False
        if self.site.getName() != other.site.getName():
            return False
        if self.site.getCustomer().getId() != other.site.getCustomer().getId():
            return False
        return True

然后你丑陋的大功能被简化为:

1
2
3
4
5
6
7
8
9
10
11
12
def site_exists(site):
   """
    returns the sitebean if it exists,
    else returns false
   """

    vpadmin_service = _get_vpadmin_service(site)
    all_sites = VpAdminServiceUtil.getSites(vpadmin_service)
    wsite = WrappedSite(site)
    for site_listing in all_sites:
        if wsite == WrappedSite(site_listing):
            return site_listing
    return False

编辑fixed site_exists返回sitebean而不是true。


在控制流中使用异常处理是一种Python式的感觉,"请求宽恕比请求许可更容易"。然而,这并没有延伸到制定规则来打破,只是为了让你可以请求宽恕。

其他人提供了很好的选择。只是想解决原则方面的问题。


使用异常处理程序作为代码目标基本上是一个"goto"。我并不认为它真的有什么问题(尽管古时候围绕着"Goto被认为是有害的"),但是知道它是Goto可能会给你一个不同的视角。


删除一些重复代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def site_exists(site):
   """
    returns the sitebean if it exists,
    else returns None
   """

    vpadmin_service = _get_vpadmin_service(site)
    all_sites = VpAdminServiceUtil.getSites(vpadmin_service)

    for site_listing in all_sites:
        if (site.getName() == site_listing.getName() and
            site.getCustomer().getId() == site_listing.getCustomer().getId()):
            if site.getId(): # if id is set; it should be the same
                if site.getId() != site_listing.getId(): continue
            else: # no id; consider it the same site
                site.setId(site_listing.getId()) #XXX side-effect
            return site_listing

注:预计site_exists()会修改其参数(通过.setId()修改)。考虑重构它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def same_site(site, other):
    if site.getId() and site.getId() != other.getId():
       # if id is set; it should be the same
       return False
    return (site.getName() == other.getName() and
            site.getCustomer().getId() == other.getCustomer().getId())

def get_site_listing(site):
   """
    returns the sitebean corresponding to `site`,
    returns None if there is none
   """

    vpadmin_service = _get_vpadmin_service(site)
    all_sites = VpAdminServiceUtil.getSites(vpadmin_service)

    return next((s for s in all_sites if same_site(site, s)), None)

注意:代码不会修改site对象。使用get_site_listing()的返回值。

如果next()不可用,则使用:

1
2
3
4
for site_listing in all_sites:
    if same_site(site, site_listing):
       return site_listing
return None

顺便说一句,jython应该给你提供property包装纸,这样你就可以写:

1
2
3
4
5
def same_site(site, other):
    if site.id and site.id != other.id:
       # if id is set; it should be the same
       return False
    return site.name == other.name and site.customer.id == other.customer.id

site.id = id代替site.setId(id)